namespace RSA.Core
{
    using System;
    using System.Collections.Generic;
    using Utils;

    public class RSACryptoProvider : ICryptoProvider
    {
        private readonly int _pseudoPrimeNumbersConfidence;

        public RSACryptoProvider()
        {
            _pseudoPrimeNumbersConfidence = 5;
        }

        public RSACryptoProvider(int pseudoPrimeNumbersConfidence)
        {
            _pseudoPrimeNumbersConfidence = pseudoPrimeNumbersConfidence;
        }

        #region Implementation of ICryptoProvider

        public void GenerateKeys(int length, out CryptoKey publicKey, out CryptoKey privateKey)
        {
            var p = BigInteger.genPseudoPrime(length, _pseudoPrimeNumbersConfidence, new Random());
            var q = BigInteger.genPseudoPrime(length, _pseudoPrimeNumbersConfidence, new Random());

            var modulus = p*q;
            var phi = (p - 1)*(q - 1);
            
            var publicFactor = phi.genCoPrime(modulus.bitCount() - 1, new Random());            
            var privateFactor = publicFactor.modInverse(phi);

            publicKey = new CryptoKey(publicFactor, modulus);
            privateKey = new CryptoKey(privateFactor, modulus);  
        }

        public byte[] Encrypt(byte[] data, CryptoKey key)
        {
            var blockSize = key.Modulus.getBytes().Length;
            
            var blockReader = new BlockIterator(blockSize, data);
            var blockFormatter = new BlockFormatter(blockSize);

            var result = new List<byte>();

            foreach (var blockOfData in blockReader)
            {
                var dataToEncrypt = new BigInteger(blockOfData);
                var encrypredBlock = dataToEncrypt.modPow(key.Factor, key.Modulus);
                var formattedBlock = blockFormatter.Format(encrypredBlock.getBytes());

                result.AddRange(formattedBlock);
            }
 
            return result.ToArray();            
        }

        public byte[] Decrypt(byte[] data, CryptoKey key)
        {
            var blockSize = key.Modulus.getBytes().Length;                    

            var blockReader = new BlockIterator(blockSize, data);

            var result = new List<byte>();

            foreach (var blockOfData in blockReader)
            {
                var dataToDecrypt = new BigInteger(blockOfData);
                var decryptedData = dataToDecrypt.modPow(key.Factor, key.Modulus);
                
                result.AddRange(decryptedData.getBytes());
            }
                       
            return result.ToArray();
        }

        #endregion
    }
}